Using Object-Oriented JavaScript to Create Multi-Use Tabs

Taking JavaScript More Seriously

As I migrated from basic HTML to JSPs and finally complex Server-Side Java, I became less and less respectful of client-side languages, and most of my colleagues were pretty much in lockstep with me. While working in the view layer, adding JavaScript was a tedious necessity, and most programmers I worked with built well-made, but structural, scripts to handle the needs of the page.  They knew what they wanted it to accomplish and they just wrote it to “do that thing”.  It “wasn’t worth” digging deep into it, because it is just “throw away” code that the view used; not really worthy of Server-Side Programmers’ attention.

A new interest in JavaScript came onto my radar screen when, as a Principal Engineer at E*******k, I was presented with a problem that involved portal pages and their downloaded page weights. there were also many problems with any changes to the page breaking the very large (600 lines at one point) JavaScript that controlled much of the view layer. 

So I looked into it.

What I found was a gigantic script that controlled all sorts of things, looping through the Document Object Model, backwards and forwards; doing some innerHTML calls, and lots of “if this is happening– do this, disable this, and if anything ends in this, make it appear” kind of things.  It worked quite well, but nobody would dare change it, make changes to the basic UI or even allow it to be touched, because it was so complex and undocumented.  Resources were unavailble to keep up with it.

Being a Principal Engineer gave me the chance to take time and solve problems instead of doing hard core, heads-down coding every day.  I decided to take some time and crack this nut.  I started digging into a few articles about Object Oriented JavaScript and did a few exercises to really try and understand how it could work for me.  I discovered that with the page I was looking at, Tabs controlled a lot about what happened on it.  Depending upon which tab was clicked, different values would appear in the box underneath it — form fields would change, be disabled, etc.

Each Tab triggered a different “Mode” on the page.  So the Page has Modes… Well that would make a nice Object. The Mode has various attributes?  Now we’re getting somewhere!  Eventually this 600 line behemoth, tipping the scales at 86K, become a 64 line midget that, condensed, popped through the pipe at 16k.  Nice win.

What I’m demonstrating here isn’t this particular code. It is the basis for it, and not only that, I think its better than what I first wrote.  I’ve tested it on Mac Safari/Firefox — it should work on IE but I wasn’t able to test, so you might need to tweak.  So much for the disclaimer.

Creating a JavaScript Model to Manipulate Tabs on a Page

code reference – version_1_tabs.zip

Let’s start with the Initial Requirement.  Let’s say you have a requirement that orders 5 tabs that change back and forth with the least amount of scripting possible, and at some later date you might have to do more with it, such as add more tabs or add functionality to each tab.  To make it more complex, the Tab has images as background, so you need to manipulate these as well.

All the images will be manipulated through CSS, so we’re not going to clutter up our beautiful HTML markup with it.  Further, we’re going to not bother with “document.write“, because it can get kinda cluttered in the JavaScript code, I’m still not sure about performance hits using it, and you don’t get any Search Engine love with that, either.  Here below is the original HTML mark-up for the initial requirement (tabpage.html):

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
    <title>Object Oriented Tabs</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <style type="text/css">
        @import url( "tabMode.css" );
    </style>

</head>
<body>
<table class="tabToggle" cellspacing="0">
    <tr>
        <td><span class="selectstring">Select Category</span></td>
        <td id="dramaTab">
            <div>
               <a class="tab_0" href="#"
                           onclick="changeMode(DRAMA_MODE);return false;">
                <span>Drama</span></a></div>
        </td>
        <td id="comedyTab">
            <div>
               <a class="tab_1" href="#"
                           onclick="changeMode(COMEDY_MODE);return false;">
                <span>Comedy</span></a></div>
        </td>
        <td id="thrillerTab">
            <div>
               <a class="tab_2" href="#"
                           onclick="changeMode(THRILLER_MODE);return false;">
                <span>Thriller</span></a></div>
        </td>
        <td id="mysteryTab">
            <div>
               <a class="tab_3" href="#"
                           onclick="changeMode(MYSTERY_MODE);return false;">
                <span>Mystery</span></a></div>
        </td>
        <td id="fantasyTab">
            <div>
               <a class="tab_4" href="#"
                           onclick="changeMode(FANTASY_MODE);return false;">
                <span>Fantasy</span></a></div>
        </td>
    </tr>
</table>
    <script type="text/javascript" src="tabtoggle.js"></script>
</body>
</html>

You will notice that for each href, there is an “onclick” value that is mapped to a const value (shown in the JavaScript file) in JavaScript.  For those of you that only code in IE, which doesn’t recognize const, it is exactly what you think.  an immutable value that is set and may not be changed.  Why is it cool?  because the browser maps it into memory, and it becomes a reference instead of a variable — faster, like a static final value in Java.  The other cool thing about it is that you can put any strings behind it, and if you’ve written your code properly, you only make a change in one place and can easily adapt your code to various conditions.  Easy to change, faster, and you’re not doing cut and paste everywhere.  Here is the JavaScript that manipulates the page (tabtoggle.js), with the constant values:

/*------------- CONSTANTS -----------------------*/
//Mode names and values
const DRAMA_MODE = "drama";
const COMEDY_MODE = "comedy";
const THRILLER_MODE = "thriller";
const MYSTERY_MODE = "mystery";
const FANTASY_MODE = "fantasy";
//initial modes
const INIT_MODE = DRAMA_MODE;
// The array of Tabs -- establishes the order
// and values used. Pretty much the whole enchilada right here.
var TAB_ARRAY = [DRAMA_MODE,COMEDY_MODE,THRILLER_MODE,MYSTERY_MODE, FANTASY_MODE];

/*-------------MODEL--------------------------*/
/* This section could be separated from the
above as a "standard" library and used against
any set of tabs or arrays with an identical
structure.*/

//Local constants specific to the model.
const TAB_ID_SUFFIX = "Tab";
const EMPTY_STR = "";
// private Tab class values for CSS
const LAST_TAB_CLASS_VALUE = "last ";
const NON_SLCT_TAB_CLASS_VALUE = "engine";
const SLCT_TAB_CLASS_VALUE = " selected";

/**
 * The Class defining the Mode of the Object
 * @param name - The name of the Tab to that
 * will receive the selected state.
 */
function TabMode(name) {
    //public accessors
    this.name = tabModes(name);
    this.modeId = name + TAB_ID_SUFFIX;

    //change all the tabs, privately.
    function tabModes(name) {
        for (var i = 0; i < TAB_ARRAY.length; i++) {
            var tid = TAB_ARRAY[i];
            var tab = new Tab(tid, (name == tid), (i == (TAB_ARRAY.length - 1)));
            document.getElementById(tab.tabId).className = tab.classVal;
        }
        return name;
    }
}

/**
 * A Tab object with needed attributes.
 * @param id -- mapped to the "id" attribute of the HTML.
 * @param selected -- boolean as to whether or not it
 * is in a "selected" state.
 * @param isLast -- boolean as to whether or not it
 * is the last tab of the series.
 */
function Tab(id, selected, isLast) {
    //accessors
    this.tabId = id + TAB_ID_SUFFIX;
    this.classVal = selectCheck();

    function selectCheck() {
        var cval = NON_SLCT_TAB_CLASS_VALUE;
        if (selected) {
            cval += SLCT_TAB_CLASS_VALUE;
        }
        if (isLast) {
            cval = LAST_TAB_CLASS_VALUE + cval;
        }
        return cval;
    }
}

/*------------- page-accessible methods -----------------------*/

//initialize mode object
var mode;

/**
 * "Public" function to change the name of the tab.
 * Accepts any tabname value mapped to the above constants.
 * @param tabname - a legal value within the TAB_ARRAY.
 */
function changeMode(tabname) {
    mode = new TabMode(tabname);
}

/**
 * Load up the initial mode.  Initializes what is
 * Shown on the page first.
 */
changeMode(INIT_MODE);

Ok, it’s more than 64 lines.  Production versions of this might have the comments stripped and some compression. It currently weighs in at 4k. The constants at the top of the file are:

//Mode names and values
const DRAMA_MODE = "drama";
const COMEDY_MODE = "comedy";
const THRILLER_MODE = "thriller";
const MYSTERY_MODE = "mystery";
const FANTASY_MODE = "fantasy";

These are mapped to the tabs, but the tabs all have a “Tab” string-value appended to it in the “id” attribute.  We’ll use this to manipulate the view elements (usually in CSS) by the “getElementByID” method.  There are two objects that are created in the JavaScript file; one is Tab, and the other is TabMode. 

What I’m doing here — the basics of declaring Objects in JavaScript

To declare an Object in JavaScript, create a function, and for best practices-sake, use an initial capital letter, similar to a Java Class Declaration.  Since we’re not dealing with a pure object-oriented language, we’ll need to declare our Object as a function:

function Tab(id, selected, isLast) {

and

function TabMode(name) {

There’s no constructor method in the class.  The function declaration contains the attributes needed to construct the Object.  Tab take three attributes — the id, a boolean for “selected”, and a boolean for “isLast” to delinieate whether or not it is the last Tab in the sequence.  You can use an Object quite easily, so let’s create some basic FooObject and demonstrate a few things.  Here’s an Object assigned to a variable:

var foo = new FooObject("funky","fruity");

This object instance can now be accessed by using foo.  What are “funky” and “fruity”?  How do we associate these to FooObject?

They have been used in the “constructor” to the Object.  The inner methods of the Object will use this information to populate accessors and can even be injected into private functions.  Let’s take a look at the actual FooObject declaration, and create some basic accessors for it:

function FooObject(smell, taste){
          this.smell = smell;
          this.taste = taste;
}

So, using the foo variable from the earlier listing, foo.smell would return “funky”, and foo.taste returns “fruity”.  Go ahead and try it, it’s just that easy. 

The Tab Object

Now back to the tabtoggle.js code. Besides accessors, I added some private functions to return some values.  The reason I made them private as I didn’t want some hackmeister coming in behind me and using these functions outside of the Object, creeping everything out on my page.  Here is the “Tab” Object code snip for discussion purposes:

function Tab(id, selected, isLast) {
    //accessors
    this.tabId = id + TAB_ID_SUFFIX;
    this.classVal = selectCheck();

    function selectCheck() {
        var cval = NON_SLCT_TAB_CLASS_VALUE;
        if (selected) {
            cval += SLCT_TAB_CLASS_VALUE;
        }
        if (isLast) {
            cval = LAST_TAB_CLASS_VALUE + cval;
        }
        return cval;
    }
}

There are two accessors in the Tab Object, created from the three parameters and some of the “constants” that were declared outside of the class and available to the entire Document. The first accessor is the “id” used for the tab, and the second is used to build a CSS class value to alter the corresponding Tab’s view.  

This second accessor, “classVal”, is interesting because it is mapped to an inner function that determines its actual value.  This function could be mapped externally to the Tab, and I’ve done this before, but after some deliberation, I decided to keep it private because I didn’t want to make it available outside of the Object.

The TabMode Object

The TabMode Object defines the Mode of the each tab.  It takes a “name” parameter that iterates through the TAB_ARRAY (Ordered Array of all Tabs) and creates Tab Objects and sets their class value through the document.getElementById() method.  I “cheated” a little here by calling this method while I set the Tab instances-  mainly to ensure that the iteration was complete (it would throw errors at this point if there is a failure).  I highlighted in red the real work that’s getting done.

/**
 * The Class defining the Mode of he Object
 * @param name - The name of the Tab to that
 * will receive the selected state.
 */
function TabMode(name) {
    //public accessors
    this.name = tabModes(name);
    this.modeId = name + TAB_ID_SUFFIX;

    //change all the tabs, privately.
    function tabModes(name) {
        for (var i = 0; i < TAB_ARRAY.length; i++) {
            var tid = TAB_ARRAY[i];
            var tab = new Tab(tid, (name == tid), (i == (TAB_ARRAY.length - 1)));
            document.getElementById(tab.tabId).className = tab.classVal;
        }
        return name;
    }
}

Everything will work now. Go ahead and open up the HTML page and click on the tabs.  We have a very nice and snappy set of tabs with only a little bit of code.  We can change the code in different ways to make it more useful.  In the next two sections, I’ll demonstrate how to use the basic JavaScript file that we’ve already defined against a completely different set of tabs with minimal code change, and then in the last section I’ll add more functionality to show/hide different information boxes when a particular tab is clicked.

Using the Same Basic Code to Populate Different Tabbed Pages

code reference – version_2_tabs.zip

We’re now going to take the code that we just wrote, create another HTML page with the same functionality but completely different tabs using the same structure, only changing the “name” constants and the “array” that they are located in.

The only big change is to separate “Page Attributes” that define a particular page for the “Model” part of the JavaScript Code.  There is no real dependency between them, and as long as there are “names” and an “array” to traverse, the tabs will pretty much work as designed against anything you want.  This allows for any number of different tabs or tab names to be used — you just attach a new file with the constants you want to use for the page, and plug in the same JavaScript Toggle Model.  For this particular exercise, we created a file that only holds the constants that we want to use for the first “tabpage1.html”.

const DRAMA_MODE = "drama";
const COMEDY_MODE = "comedy";
const THRILLER_MODE = "thriller";
const MYSTERY_MODE = "mystery";
const FANTASY_MODE = "fantasy";
//initial modes
const INIT_MODE = DRAMA_MODE;
// The array of Tabs -- establishes the order
// and values used. Pretty much the whole enchilada right here.
var TAB_ARRAY = [DRAMA_MODE,COMEDY_MODE,THRILLER_MODE,
                 MYSTERY_MODE, FANTASY_MODE];

// private Tab class values for CSS
const LAST_TAB_CLASS_VALUE = "last ";
const NON_SLCT_TAB_CLASS_VALUE = "engine";
const SLCT_TAB_CLASS_VALUE = " selected";

No structural code changes were made. I just “cut” the script from the first section in two, putting the constants that are mutable away from all of the code that doesn’t need to change.  If we want to create a different page, we’ll be able to use the exact same “model” code and only change the code in the listing above to suit our needs.  The Model part of the code is still located in the “tabtoggle.js” file. 

Since there’s no change, I’ve truncated the listing below only to provide clarity as to where the “split” occurs.  refer to the version_2_tabs.zip for more granularity.

/*-------------MODEL--------------------------*/

//Local constants specific to the model.
const TAB_ID_SUFFIX = "Tab";
const EMPTY_STR = "";

function TabMode(name) {
  ... (no changes to code -- see details in zipped download)
}

function Tab(id, selected, isLast) {

... (no changes to code -- see details in zipped download)}

/*-------- page-accessible methods -----------*/

//initialize mode object
var mode;

function changeMode(tabname) {
    mode = new TabMode(tabname);
}

changeMode(INIT_MODE);

Again, no changes. We’ve left the “model” part of the code at the bottom of the page to load just before the </body> tag, and the “Page Attributes” are coded to load in the <head> of the document.  Other than that, the HTML is unchanged in the “tabpage1.html”.  The tabs will work and fire identically as they did before.

But now, we get to do something cool.  We create a new HTML file, “tabpage2.html”, and put completely different tab names in them.  Not only are they different names, they are even more of them!  The underlying model code will handle them perfectly, as all it cares about is the array mapped to the constant names.  The array provides the order and a looping mechanism, and the constants provide a way to change things without making changes all over the place.

The new HTML code contains different cartoon shows or characters mapped to the same html pattern as “tabpage1.html”.  Here is the new table with the tabs.  Changes are in red:

</head>
<body>
<table class="tabToggle" cellspacing="0">
    <tr>
        <td><span class="selectstring">Select Category</span></td>
        <td id="bullwinkleTab">
            <div><a class="tab_0" href="#" onclick="changeMode(BULLWINKLE_MODE);return false;">
                <span>Bullwinkle</span></a></div>
        </td>
        <td id="tomandjerryTab">
            <div><a class="tab_1" href="#" onclick="changeMode(TOM_AND_JERRY_MODE);return false;">
                <span>Tom and Jerry</span></a></div>
        </td>
        <td id="simpsonsTab">
            <div><a class="tab_2" href="#" onclick="changeMode(SIMPSONS_MODE);return false;">
                <span>Simpsons</span></a></div>
        </td>
        <td id="futuramaTab">
            <div><a class="tab_3" href="#" onclick="changeMode(FUTURAMA_MODE);return false;">
                <span>Futurama</span></a></div>
        </td>
        <td id="bugsbunnyTab">
            <div><a class="tab_4" href="#" onclick="changeMode(BUGSBUNNY_MODE);return false;">
                <span>Bugs Bunny</span></a></div>
        </td>
        <td id="mickeymouseTab">
            <div><a class="tab_5" href="#" onclick="changeMode(MICKEYMOUSE_MODE);return false;">
                <span>Mickey Mouse</span></a></div>
        </td>
        <td id="barneybearTab">
            <div><a class="tab_6" href="#" onclick="changeMode(BARNEYBEAR_MODE);return false;">
                <span>Barney Bear</span></a></div>
        </td>
        <td id="ScoobyDooTab">
            <div><a class="tab_7" href="#" onclick="changeMode(SCOOBYDOO_MODE);return false;">
                <span>Scooby Doo</span></a></div>
        </td>
    </tr>
</table>
<script type="text/javascript" src="tabtoggle.js"></script>
</body>
</html>

Note the script reference at the bottom of the page.  It only contains the “Model” code.  The new “Page Attributes” are shown below, contained in the file tabpage2.js.  New code is in red.  Although there is some “cut and paste” code in here, it’s because we might want to change the class names of the tabs to vary the view, and this would be the best place to keep it.:

/*
 * Copyright (c) 2008, Your Corporation. All Rights Reserved.
 */
/*------------- CONSTANTS -----------------------*/
//Mode names and values
const BULLWINKLE_MODE = "bullwinkle";
const TOM_AND_JERRY_MODE = "tomandjerry";
const SIMPSONS_MODE = "simpsons";
const FUTURAMA_MODE = "futurama";
const BUGSBUNNY_MODE = "bugsbunny";
const MICKEYMOUSE_MODE = "mickeymouse";
const BARNEYBEAR_MODE = "barneybear";
const SCOOBYDOO_MODE = "ScoobyDoo";
//initial modes
const INIT_MODE = BULLWINKLE_MODE;
// The array of Tabs -- establishes the order
// and values used. Pretty much the whole enchilada right here.
var TAB_ARRAY = [BULLWINKLE_MODE,
    TOM_AND_JERRY_MODE,SIMPSONS_MODE,FUTURAMA_MODE,
    BUGSBUNNY_MODE,MICKEYMOUSE_MODE,BARNEYBEAR_MODE,
    SCOOBYDOO_MODE];

// private Tab class values for CSS
const LAST_TAB_CLASS_VALUE = "last ";
const NON_SLCT_TAB_CLASS_VALUE = "engine";
const SLCT_TAB_CLASS_VALUE = " selected";

When you load the the “tabpage2.html” in the browser, the new tabs will appear, with new names, more of them, but with exactly the same functionality. The underlying model works just as it did with the tabs on the other page, with only the inputs changed. You could put the inputs on the page or load it into a file that is picked up by the page as we did here  I believe either is fine – your decision where to put them is based upon your requirements and judgement.  No wrong answers!

Adding A Content Box Below the Tabs

code reference — version_3_tabs.zip

Finally, we get a requirement to load a different but corresponding content box to the tabs we have created.  Let’s also say that our requirement states that only some of the tabs will have variable content boxes, other functionality may be required elsewhere.  We want to add a content show-hide feature to the boxes, yet make sure that any tabbed page using our code doesn’t throw errors, as we just hate that.   The style attribute we will chose is “visiblity“, and we’ll use the “none” and “inline” values — since “none” will take the object out of formatting and not affect the flow of the page. “inline” will put the box in the flow. To change the style attribute trigger, we’ll use:

document.getElementById("foo").style.display = ...

It is necessary to loop through all of the tabs whenever the page mode is changed to make sure that the right one is “turned on” and the others are invisible.  Arriving at this functionality is actually quite easy.  We’ll just add this to the existing private function in the TabMode Object.

A new HTML page is similar to the others we’ve created, with the changes added in the listing below (changes in red, surrounded by existing code for reference).  The “outer” div for each box is what controls the visibility, the “inner” div with the class attribute “underbox” is for decorating the view, and has the class value mapped into the css file to control it.  Each “id” attribute in the “outer” box maps to a tab “name” attribute, the “Box” is the suffix for this id and is used within the JavaScript code, e.g. dramaBox, comedyBox, etc.

       <td id="fantasyTab">
            <div><a class="tab_4" href="#" onclick="changeMode(FANTASY_MODE);return false;">
                <span>Fantasy</span></a></div>
        </td>
    </tr>
</table>
<!-- Add a Box! -->
<div id="dramaBox">
    <div class="underbox">
        <h2>Drama</h2>
        Lorem ipsum dolor sit amet,...
        zzril delenit augue duis dolore te feugait
        nulla facilisi.
    </div>
</div>
<div id="comedyBox">
    <div class="underbox">
        <h2>Comedy</h2>
        Darkness washed over the Dude, darker than a steer's
        tukus on a moonless night.
    </div>
</div>
<div id="thrillerBox">
    <div class="underbox">
        <h2>Thriller</h2>
        ... So I made a decision, and it
        was... wrong. It was a bad call, Ripley. It was a bad call..
    </div>
</div>
<div id="mysteryBox">
    <div class="underbox">
        <h2>Mystery</h2>
        Some text here
    </div>
</div>
<div id="fantasyBox">
    <div class="underbox">
        <h2>Fantasy</h2>
        Some text here...
    </div>
</div>
<script type="text/javascript" src="tabtoggle.js"></script>
</body>
</html>

Here is the code for the enhanced tabs in the file tabtoggle.js. The only change needed is to add a few constants and a small conditional routine in the TabMode Object.  It will actually detect if the boxes are present to toggle their visibility, otherwise this functionality is ignored. The changed code is in red.

const BOX_ID_SUFFIX = "Box";
//Display box values
const YES_DISPLAY = "inline";
const NO_DISPLAY = "none";

/**
 * The Class defining the Mode of he Object. Alters the tab Selected,
 * and also changes which box should be displayed under it, IF a box exists.
 * @param name - The name of the Tab to that
 * will receive the selected state.
 */
function TabMode(name) {
    //public accessors
    this.name = tabModes(name);
    this.modeId = name + TAB_ID_SUFFIX;

    //change all the tabs, privately.
    function tabModes(name) {
        for (var i = 0; i < TAB_ARRAY.length; i++) {
            var tid = TAB_ARRAY[i];
            var tab = new Tab(tid, (name == tid), (i == (TAB_ARRAY.length - 1)));
            document.getElementById(tab.tabId).className = tab.classVal;
        }
        //This changes out the boxes, if they exist...
        if (document.getElementById(name + BOX_ID_SUFFIX) != null) {
            for (var i = 0; i < TAB_ARRAY.length; i++) {
                var tid = TAB_ARRAY[i];
                if (tid == name) {
      document.getElementById(tid+ BOX_ID_SUFFIX).style.display = YES_DISPLAY;
                } else {
      document.getElementById(tid+ BOX_ID_SUFFIX).style.display = NO_DISPLAY;
                }
            }
        }
        return name;
    }
}

The code above will now allow the choosing of each tab and the display of different boxes below. Not only that, but when applied to our second, “cartoon tabs”, page, no errors will be thrown since there is a “check” as to whether the boxes exist first.  If you want to add boxes to the other page, you just map them to their tab names and use the “Box” instead of “Tab” suffix!

Conclusions:

Using the JavaScript Prototype element can extend the above code in even more directions.  Want to have a form connected to each tab?  Do they have different elements?  You can enable and disable them by extending the above classes to do even more.  A “TabbedForm” class could contain the form elements and keep track of what to enable and disable, different submission urls or even more.  You don’t have to alter the base class, you just extend it using the Prototype Element.

One of the truly wonderful things about JavaScript is the fact that there are many ways to the same result.  What I find interesting is how people can come up with ideas to make their scripts smaller, more re-usable and extendable.  Creating Objects in JavaScript allow for power that many mark-up engineers don’t know about to this day;  a lot of server-side programmers, myself included, begin to take this client side language seriously and realize the power, and fun, of writing this type of code.

One thought on “Using Object-Oriented JavaScript to Create Multi-Use Tabs

  1. Pingback: Recent Links Tagged With "visiblity" - JabberTags

Leave a comment